package drawing {
	
	import flash.display.CapsStyle;
	import flash.display.JointStyle;
	import flash.display.LineScaleMode;
	import flash.display.Sprite;
	import flash.geom.Point;
	
	import utilities.uMath;
	
	/**
	 * BezierCurve
	 * 
	 * BezierCurve is a very small class for drawing Bézier curves. Flash's Graphics package
	 * offers Quadratic Bézier curves with Graphics.curveTo() but these only allow one control
	 * point. BezierCurve offers the use of more control points. Supply two anchor points and
	 * two control points for instance, one could draw an approximation of a circle with a
	 * maximum radial error of less than one part in a thousand.
	 * 
	 * @package		components
	 * @author		Nathan Lucas <monk4688@gmail.com>
	 * @link		http://code.google.com/p/beziercurveas3/
	 * @version		1.2
	 */
	public class BezierCurve {
		
		/**
		 * Point array
		 * 
		 * @access	private
		 */
		private var _points:Array;
		
		/**
		 * Precision array
		 * 
		 * @access	private
		 */
		private var _t:Array = [];
		
		/**
		 * BezierCurve
		 * 
		 * @access	public
		 * @param	points		Array of curve's anchor and control points.
		 * @param	precision	Precision of curve, default 100.
		 * @return	BezierCurve
		 */
		public function BezierCurve(points:Array, precision:Number = 100) {
			_points = points;
			
			for (var i:int = 0; i <= precision; i++) {
				_t.push(Math.pow((i / precision), 1.01));
			}
		}
		
		/**
		 * Get fraction 0..1 at specified point of 0..precision in curve.
		 * 
		 * @access	public
		 * @param	t		Fraction at _t[x] curve precision.
		 * @return	Number
		 */
		public function t(x:Number):Number {
			return _t[x];
		}
		
		/**
		 * Draw the complete curve on given Sprite(target)
		 * 
		 * @access	public
		 * @param	target		Target Sprite to draw on.
		 * @param	thickess	Line thickness.
		 * @param	color		Line color.
		 * @param	alpha		Line alpha.
		 * @return	void
		 */
		public function draw(target:Sprite, thickness:Number = 1, color:uint = 0x000000, alpha:Number = 1):void {
			target.graphics.lineStyle(thickness, color, alpha, false, LineScaleMode.NONE, CapsStyle.NONE, JointStyle.ROUND);
			var p:Point;
			for (var t:int = 0; t < _t.length; t++) {
				p = getCurvePoint(t);
				if (t == 0) {
					target.graphics.moveTo(p.x, p.y);
				} else {
					target.graphics.lineTo(p.x, p.y);
				}
			}
		}
		
		/**
		 * Returns point at given fraction 0..precision of curve.
		 * 
		 * @access	public
		 * @param	t		Fraction at _t[x] curve precision.
		 * @return	Point
		 */
		public function getCurvePoint(t:Number):Point {
			var n = (_points.length - 1);
			var p:Array = uMath.sum(0, n, function(i:Number):Array {
				var b:Number = uMath.bernstein(n, i, _t[t]);
				return [(b * _points[i].x), (b * _points[i].y)];
			});
			return new Point(p[0], p[1]);
		}
		
		/**
		 * de Casteljau's recursive algorithm for evaluating the curve.
		 * 
		 * This returns an array like: (If 4 points in the curve.)
		 * [[Point, Point, Point],
		 * 	[Point, Point],
		 * 	[Point]];
		 * 
		 * @access	public
		 * @param	t		Fraction at _t[x] curve precision.
		 * @return	Array
		 * @see <http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm>
		 */
		public function casteljau(t:Number):Array {
			var o:Array = _points;
			var n:Array = [];
			var r:Array = [];
			var c:Number;
			do {
				for (var i:Number = 0; i < (o.length - 1); i++) {
					var _1:Point = uMath.scalePoint(o[i], (1 - _t[t]));
					var _2:Point = uMath.scalePoint(o[i + 1], _t[t]);
					n.push(uMath.addPoints(_1, _2));
				}
				c = n.length;
				r.push(n);
				o = n;
				n = [];
			} while (c > 1);
			return r;
		}
	}
}